const MIN_ROWS = 4;
const INACTIVE_GRID_OPACITY = 77;
-const INACTIVE_GRID_OPACITY_ANIMATION_TIME = 0.40;
+// This time needs to be less than IconGrid.EXTRA_SPACE_ANIMATION_TIME
+// to not clash with other animations
+const INACTIVE_GRID_OPACITY_ANIMATION_TIME = 0.24;
const FOLDER_SUBICON_FRACTION = .4;
const MIN_FREQUENT_APPS_COUNT = 3;
this.selectApp(id);
}));
}
+ },
+
+ _doSpringAnimation: function(animationDirection) {
+ this._grid.actor.opacity = 255;
+ this._grid.animateSpring(animationDirection,
+ Main.overview.getShowAppsButton());
+ },
+
+ animate: function(animationDirection, onComplete) {
+ if (animationDirection == IconGrid.AnimationDirection.IN) {
+ let toAnimate = this._grid.actor.connect('notify::allocation', Lang.bind(this,
+ function() {
+ this._grid.actor.disconnect(toAnimate);
+ // We need to hide the grid temporary to not flash it
+ // for a frame
+ this._grid.actor.opacity = 0;
+ Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
+ Lang.bind(this, function() {
+ this._doSpringAnimation(animationDirection)
+ }));
+ }));
+ } else {
+ this._doSpringAnimation(animationDirection);
+ }
+
+ if (onComplete) {
+ let animationDoneId = this._grid.connect('animation-done', Lang.bind(this,
+ function () {
+ this._grid.disconnect(animationDoneId);
+ onComplete();
+ }));
+ }
}
});
Signals.addSignalMethods(BaseAppView.prototype);
this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
let box = new St.BoxLayout({ vertical: true });
- this._currentPage = 0;
+ this._grid.currentPage = 0;
this._stack.add_actor(this._grid.actor);
this._eventBlocker = new St.Widget({ x_expand: true, y_expand: true });
this._stack.add_actor(this._eventBlocker);
this._refilterApps();
},
+ // Overriden from BaseAppView
+ animate: function (animationDirection, onComplete) {
+ if (animationDirection == IconGrid.AnimationDirection.OUT &&
+ this._displayingPopup && this._currentPopup) {
+ this._currentPopup.popdown();
+ let spaceClosedId = this._grid.connect('space-closed', Lang.bind(this,
+ function() {
+ this._grid.disconnect(spaceClosedId);
+ // Given that we can't call this.parent() inside the
+ // signal handler, call again animate which will
+ // call the parent given that popup is already
+ // closed.
+ this.animate(animationDirection, onComplete);
+ }));
+ } else {
+ this.parent(animationDirection, onComplete);
+ }
+ },
+
getCurrentPageY: function() {
- return this._grid.getPageY(this._currentPage);
+ return this._grid.getPageY(this._grid.currentPage);
},
goToPage: function(pageNumber) {
pageNumber = clamp(pageNumber, 0, this._grid.nPages() - 1);
- if (this._currentPage == pageNumber && this._displayingPopup && this._currentPopup)
+ if (this._grid.currentPage == pageNumber && this._displayingPopup && this._currentPopup)
return;
if (this._displayingPopup && this._currentPopup)
this._currentPopup.popdown();
let time;
// Only take the velocity into account on page changes, otherwise
// return smoothly to the current page using the default velocity
- if (this._currentPage != pageNumber) {
+ if (this._grid.currentPage != pageNumber) {
let minVelocity = totalHeight / (PAGE_SWITCH_TIME * 1000);
velocity = Math.max(minVelocity, velocity);
time = (diffToPage / velocity) / 1000;
// longer than PAGE_SWITCH_TIME
time = Math.min(time, PAGE_SWITCH_TIME);
- this._currentPage = pageNumber;
+ this._grid.currentPage = pageNumber;
Tweener.addTween(this._adjustment,
- { value: this._grid.getPageY(this._currentPage),
+ { value: this._grid.getPageY(this._grid.currentPage),
time: time,
transition: 'easeOutQuad' });
this._pageIndicators.setCurrentPage(pageNumber);
let direction = event.get_scroll_direction();
if (direction == Clutter.ScrollDirection.UP)
- this.goToPage(this._currentPage - 1);
+ this.goToPage(this._grid.currentPage - 1);
else if (direction == Clutter.ScrollDirection.DOWN)
- this.goToPage(this._currentPage + 1);
+ this.goToPage(this._grid.currentPage + 1);
return Clutter.EVENT_STOP;
},
return Clutter.EVENT_STOP;
if (event.get_key_symbol() == Clutter.Page_Up) {
- this.goToPage(this._currentPage - 1);
+ this.goToPage(this._grid.currentPage - 1);
return Clutter.EVENT_STOP;
} else if (event.get_key_symbol() == Clutter.Page_Down) {
- this.goToPage(this._currentPage + 1);
+ this.goToPage(this._grid.currentPage + 1);
return Clutter.EVENT_STOP;
}
if (this._availWidth != availWidth || this._availHeight != availHeight || oldNPages != this._grid.nPages()) {
this._adjustment.value = 0;
- this._currentPage = 0;
+ this._grid.currentPage = 0;
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this,
function() {
this._pageIndicators.setNPages(this._grid.nPages());
let layout = new ControlsBoxLayout({ homogeneous: true });
this._controls = new St.Widget({ style_class: 'app-view-controls',
layout_manager: layout });
+ this._controls.connect('notify::mapped', Lang.bind(this,
+ function() {
+ // controls are faded either with their parent or
+ // explicitly in animate(); we can't know how they'll be
+ // shown next, so make sure to restore their opacity
+ // when they are hidden
+ if (this._controls.mapped)
+ return;
+
+ Tweener.removeTweens(this._controls);
+ this._controls.opacity = 255;
+ }));
+
layout.hookup_style(this._controls);
this.actor.add_actor(new St.Bin({ child: this._controls }));
this._updateFrequentVisibility();
},
+ animate: function(animationDirection, onComplete) {
+ let currentView = this._views[global.settings.get_uint('app-picker-view')].view;
+
+ // Animate controls opacity using iconGrid animation time, since
+ // it will be the time the AllView or FrequentView takes to show
+ // it entirely.
+ let finalOpacity;
+ if (animationDirection == IconGrid.AnimationDirection.IN) {
+ this._controls.opacity = 0;
+ finalOpacity = 255;
+ } else {
+ finalOpacity = 0
+ }
+
+ Tweener.addTween(this._controls,
+ { time: IconGrid.ANIMATION_TIME_IN,
+ transition: 'easeInOutQuad',
+ opacity: finalOpacity,
+ });
+
+ currentView.animate(animationDirection, onComplete);
+ },
+
_showView: function(activeIndex) {
for (let i = 0; i < this._views.length; i++) {
let actor = this._views[i].view.actor;
Util.ensureActorVisibleInScrollView(this.actor, actor);
},
+ // Overriden from BaseAppView
animate: function(animationDirection) {
this._grid.animatePulse(animationDirection);
},
const EXTRA_SPACE_ANIMATION_TIME = 0.25;
const ANIMATION_TIME_IN = 0.350;
+const ANIMATION_TIME_OUT = 1/2 * ANIMATION_TIME_IN;
const ANIMATION_MAX_DELAY_FOR_ITEM = 2/3 * ANIMATION_TIME_IN;
+const ANIMATION_MAX_DELAY_OUT_FOR_ITEM = 2/3 * ANIMATION_TIME_OUT;
+const ANIMATION_FADE_IN_TIME_FOR_ITEM = 1/4 * ANIMATION_TIME_IN;
const ANIMATION_BOUNCE_ICON_SCALE = 1.1;
const AnimationDirection = {
- IN: 0
+ IN: 0,
+ OUT: 1
};
const BaseIcon = new Lang.Class({
}
},
+ animateSpring: function(animationDirection, sourceActor) {
+ if (this._animating)
+ return;
+
+ this._animating = true;
+
+ let actors = this._getChildrenToAnimate();
+ if (actors.length == 0) {
+ this._animationDone();
+ return;
+ }
+
+ let [sourceX, sourceY] = sourceActor.get_transformed_position();
+ let [sourceWidth, sourceHeight] = sourceActor.get_size();
+ // Get the center
+ let [sourceCenterX, sourceCenterY] = [sourceX + sourceWidth / 2, sourceY + sourceHeight / 2];
+ // Design decision, 1/2 of the source actor size.
+ let [sourceScaledWidth, sourceScaledHeight] = [sourceWidth / 2, sourceHeight / 2];
+
+ actors.forEach(function(actor) {
+ let [actorX, actorY] = actor._transformedPosition = actor.get_transformed_position();
+ let [x, y] = [actorX - sourceX, actorY - sourceY];
+ actor._distance = Math.sqrt(x * x + y * y);
+ });
+ let maxDist = actors.reduce(function(prev, cur) {
+ return Math.max(prev, cur._distance);
+ }, 0);
+ let minDist = actors.reduce(function(prev, cur) {
+ return Math.min(prev, cur._distance);
+ }, Infinity);
+ let normalization = maxDist - minDist;
+
+ for (let index = 0; index < actors.length; index++) {
+ let actor = actors[index];
+ actor.opacity = 0;
+
+ let actorClone = new Clutter.Clone({ source: actor });
+ Main.uiGroup.add_actor(actorClone);
+
+ let [width, height,,] = this._getAllocatedChildSizeAndSpacing(actor);
+ actorClone.set_size(width, height);
+ let scaleX = sourceScaledWidth / width;
+ let scaleY = sourceScaledHeight / height;
+ let [adjustedSourcePositionX, adjustedSourcePositionY] = [sourceCenterX - sourceScaledWidth / 2, sourceY - sourceScaledHeight / 2];
+
+ // Defeat onComplete anonymous function closure
+ let isLastItem = index == actors.length - 1;
+
+ let movementParams, fadeParams;
+ if (animationDirection == AnimationDirection.IN) {
+ actorClone.opacity = 0;
+ actorClone.set_scale(scaleX, scaleY);
+
+ actorClone.set_position(adjustedSourcePositionX, adjustedSourcePositionY);
+
+ let delay = (1 - (actor._distance - minDist) / normalization) * ANIMATION_MAX_DELAY_FOR_ITEM;
+ let [finalX, finalY] = actor._transformedPosition;
+ movementParams = { time: ANIMATION_TIME_IN,
+ transition: 'easeInOutQuad',
+ delay: delay,
+ x: finalX,
+ y: finalY,
+ scale_x: 1,
+ scale_y: 1,
+ onComplete: Lang.bind(this, function() {
+ if (isLastItem)
+ this._animationDone();
+
+ actor.opacity = 255;
+ actorClone.destroy();
+ })};
+ fadeParams = { time: ANIMATION_FADE_IN_TIME_FOR_ITEM,
+ transition: 'easeInOutQuad',
+ delay: delay,
+ opacity: 255 };
+ } else {
+ let [startX, startY] = actor._transformedPosition;
+ actorClone.set_position(startX, startY);
+
+ let delay = (actor._distance - minDist) / normalization * ANIMATION_MAX_DELAY_OUT_FOR_ITEM;
+ movementParams = { time: ANIMATION_TIME_OUT,
+ transition: 'easeInOutQuad',
+ delay: delay,
+ x: adjustedSourcePositionX,
+ y: adjustedSourcePositionY,
+ scale_x: scaleX,
+ scale_y: scaleY,
+ onComplete: Lang.bind(this, function() {
+ if (isLastItem) {
+ this._animationDone();
+ this._restoreItemsOpacity();
+ }
+ actorClone.destroy();
+ })};
+ fadeParams = { time: ANIMATION_FADE_IN_TIME_FOR_ITEM,
+ transition: 'easeInOutQuad',
+ delay: ANIMATION_TIME_OUT + delay - ANIMATION_FADE_IN_TIME_FOR_ITEM,
+ opacity: 0 };
+ }
+
+
+ Tweener.addTween(actorClone, movementParams);
+ Tweener.addTween(actorClone, fadeParams);
+ }
+ },
+
+ _restoreItemsOpacity: function() {
+ for (let index = 0; index < this._items.length; index++) {
+ this._items[index].actor.opacity = 255;
+ }
+ },
+
_getAllocatedChildSizeAndSpacing: function(child) {
let [,, natWidth, natHeight] = child.get_preferred_size();
let width = Math.min(this._getHItemSize(), natWidth);
_init: function(params) {
this.parent(params);
this._nPages = 0;
+ this.currentPage = 0;
this._rowsPerPage = 0;
this._spaceBetweenPages = 0;
this._childrenPerPage = 0;
}
},
+ // Overriden from IconGrid
+ _getChildrenToAnimate: function() {
+ let children = this._getVisibleChildren();
+ let firstIndex = this._childrenPerPage * this.currentPage;
+ let lastIndex = firstIndex + this._childrenPerPage;
+
+ return children.slice(firstIndex, lastIndex);
+ },
+
_computePages: function (availWidthPerPage, availHeightPerPage) {
let [nColumns, usedWidth] = this._computeLayout(availWidthPerPage);
let nRows;
this.hide();
else
this.show();
+ },
+
+ getShowAppsButton: function() {
+ return this._dash.showAppsButton;
}
});
Signals.addSignalMethods(Overview.prototype);
const Tweener = imports.ui.tweener;
const WorkspacesView = imports.ui.workspacesView;
const EdgeDragAction = imports.ui.edgeDragAction;
+const IconGrid = imports.ui.iconGrid;
const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
return page;
},
- _fadePageIn: function(oldPage) {
+ _fadePageIn: function() {
+ Tweener.addTween(this._activePage,
+ { opacity: 255,
+ time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
+ transition: 'easeOutQuad'
+ });
+ },
+
+ _fadePageOut: function(page) {
+ let oldPage = page;
+ Tweener.addTween(page,
+ { opacity: 0,
+ time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
+ transition: 'easeOutQuad',
+ onComplete: Lang.bind(this, function() {
+ this._animateIn(oldPage);
+ })
+ });
+ },
+
+ _animateIn: function(oldPage) {
if (oldPage)
oldPage.hide();
this.emit('page-empty');
this._activePage.show();
- Tweener.addTween(this._activePage,
- { opacity: 255,
- time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
- transition: 'easeOutQuad'
- });
+
+ if (this._activePage == this._appsPage && oldPage == this._workspacesPage) {
+ // Restore opacity, in case we animated via _fadePageOut
+ this._activePage.opacity = 255;
+ this.appDisplay.animate(IconGrid.AnimationDirection.IN);
+ } else {
+ this._fadePageIn();
+ }
+ },
+
+ _animateOut: function(page) {
+ let oldPage = page;
+ if (page == this._appsPage &&
+ this._activePage == this._workspacesPage &&
+ !Main.overview.animationInProgress) {
+ this.appDisplay.animate(IconGrid.AnimationDirection.OUT, Lang.bind(this,
+ function() {
+ this._animateIn(oldPage)
+ }));
+ } else {
+ this._fadePageOut(page);
+ }
},
_showPage: function(page) {
this.emit('page-changed');
if (oldPage)
- Tweener.addTween(oldPage,
- { opacity: 0,
- time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
- transition: 'easeOutQuad',
- onComplete: Lang.bind(this,
- function() {
- this._fadePageIn(oldPage);
- })
- });
+ this._animateOut(oldPage)
else
- this._fadePageIn();
+ this._animateIn();
},
_a11yFocusPage: function(page) {